﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Xml.Serialization;
using VocabUtils;

namespace VocabLib
{
	public class VocabModel
	{
		[Serializable]
		[XmlRoot("Vocab")]
		public class Record : IComparable<Record>
		{
			public string Hun { get; set; }
			public string Eng { get; set; }
			public int TotalGuesses { get; set; }
			public int SuccessfulGuesses { get; set; }
			public int SuccessfulGuessesInARow { get; set; }

			public double HitRatio
			{
				get
				{
					if (TotalGuesses == 0)
					{
						return 0;
					}
					else
					{
						return (double)SuccessfulGuesses / TotalGuesses;
					}
				}
			}

			public double Priority
			{
				get
				{
					var timeSpan = DateTime.Now - LastAsked;
					var longTimeScore = (double)Math.Min(timeSpan.TotalMinutes, 1000) / 1000;
					var shortTimeScore = (double)Math.Min(timeSpan.TotalSeconds, 10 * 60) / (10 * 60);
					// We consider a word learnt, if there are 7 successful guesses in a row
					double learntQuestionCount = 7;
					double a = -1.0 / (learntQuestionCount * learntQuestionCount);
					var hitScore = Math.Max(0.05, a * SuccessfulGuessesInARow * SuccessfulGuessesInARow + 1);
					var guessScore = 1 - HitRatio;
					var newScore = TotalGuesses <= 2 ? 1 / (TotalGuesses + 1) : 0;

					return hitScore * (guessScore * 1.5 + longTimeScore + shortTimeScore + newScore * 1.5);
				}
			}
			public DateTime LastAsked { get; set; }
			public DateTime FirstLoaded { get; set; }

			private double rand;

			public Record() { }

			public Record(string hun, string eng, DateTime firstLoaded)
			{
				TotalGuesses = 0;
				SuccessfulGuesses = 0;
				Hun = hun;
				Eng = eng;
				LastAsked = DateTime.Now;
				FirstLoaded = firstLoaded;
				Random r = new Random();
				rand = r.NextDouble();
			}

			public void AppendEng(string newEng)
			{
				Eng = Eng + ", " + newEng;
			}

			public bool Guess(string guess)
			{
				LastAsked = DateTime.Now;
				var guessWords =
					guess.Split(',').Select(WordUtils.TrimPrepositions).Select(word => word.ToLower()).OrderBy(key => key).ToArray();
				var engWords =
					Eng.Split(',').Select(WordUtils.TrimPrepositions).Select(word => word.ToLower()).OrderBy(key => key).ToArray();

				// Compare the two arrays, they should be equal but may be in different order
				bool success = guessWords.SequenceEqual(engWords);

				TotalGuesses++;
				if (success)
				{
					SuccessfulGuesses++;
					SuccessfulGuessesInARow++;
				}
				else
				{
					SuccessfulGuessesInARow = 0;
				}

				return success;
			}

			public int CompareTo(Record other)
			{
				if (this.Priority <= other.Priority) return -1;
				else if (this.Priority > other.Priority) return 1;
				else return 0;
			}

			internal void Clear()
			{
				TotalGuesses = 0;
				SuccessfulGuesses = 0;
				this.LastAsked = DateTime.Now;
			}
		};

		public class Vocab
		{
			public int SerializableFormatVersion { get; set; }
			//[XmlArray("RecordList"), XmlArrayItem(typeof(Record), ElementName = "Record")]
			public SerializableDictionary<string, Record> HunEngDic { get; set; }

			public int TotalWordPairCount
			{
				get
				{
					return this.HunEngDic.Count;
				}
			}

			public double PositiveCoverage
			{
				get
				{
					int correctAnswerCount = this.HunEngDic.Sum(kv => kv.Value.SuccessfulGuesses);

					return (double)correctAnswerCount / TotalWordPairCount;
				}
			}

			public int LearnedWordCount
			{
				get
				{
					return HunEngDic.Count(record => record.Value.SuccessfulGuessesInARow >= 3 && (DateTime.Now - record.Value.LastAsked).TotalDays < 15);
				}
			}

			public int NotKnownWordCount
			{
				get { return HunEngDic.Count(record => record.Value.SuccessfulGuesses == 0); }
			}

			[XmlIgnore]
			public List<Record> WorstWords
			{
				get { return HunEngDic.Select(kv => kv.Value).OrderByDescending(r => r.Priority).ToList(); }
			}

			[XmlIgnore]
			public List<Record> NotKnownWords
			{
				get { return HunEngDic.Where(record => record.Value.SuccessfulGuesses == 0).Select(kv => kv.Value).OrderByDescending(r => r.Priority).ToList(); }
			}

			public Vocab()
			{
				this.SerializableFormatVersion = 2;
				this.HunEngDic = new SerializableDictionary<string, Record>();
			}

			public void ClearUsages()
			{
				foreach (var kv in this.HunEngDic)
				{
					kv.Value.Clear();
				}
			}

			public void RefreshRecord(string oldHun, string newHun, string eng)
			{
				Record rec;
				if (!HunEngDic.TryGetValue(oldHun, out rec)) return;

				HunEngDic.Remove(oldHun);
				rec.Eng = eng;
				rec.Hun = newHun;
				if (HunEngDic.ContainsKey(newHun))
				{
					HunEngDic.Remove(newHun);
				}
				HunEngDic.Add(newHun, rec);
			}
		}
		public Vocab Vocabulary { get; set; }

		public VocabModel()
		{
			Vocabulary = new Vocab();
		}

		public Record GetNextQuestion()
		{
			double fullScore = Vocabulary.HunEngDic.Sum(kv => kv.Value.Priority);

			Random rand = new Random();
			double r = rand.NextDouble();

			double sum = 0.0;

			foreach (var kv in Vocabulary.HunEngDic)
			{
				sum += kv.Value.Priority;

				if (sum / fullScore > r)
				{
					return kv.Value;
				}
			}

			return null;
		}

		public void MergeDictionary(string filePath)
		{
			if (!File.Exists(filePath))
				return;

			try
			{
				string[] lines = File.ReadAllLines(filePath, Encoding.UTF8);

				foreach (var line in lines)
				{
					var tokens = line.Split(';');
					var eng = WordUtils.TrimWhitespaces(tokens[0]);
					var hun = WordUtils.TrimWhitespaces(tokens[1]);

					Record rec;
					if (Vocabulary.HunEngDic.TryGetValue(hun, out rec))
					{
						if (hun == "")
						{
							Vocabulary.HunEngDic.Remove(hun);
							continue;
						}

						double similarity = WordUtils.GetSimilarity(eng, rec.Eng);

						if (similarity > 0.99)
						{
							continue;
						}
						else if (similarity < 0.5)
						{
							// Append the new English word to the current one if it is not similar
							rec.AppendEng(eng);
						}
						else
						{
							// Rewrite
							rec.Eng = eng;
						}
					}
					else
					{
						if (hun != "")
						{
							Vocabulary.HunEngDic[hun] = new Record(hun, eng, DateTime.Now);
						}
					}
				}
			}
			catch (Exception exception)
			{
				MessageBox.Show("Merging failed: " + exception.ToString());
			}
		}

		public void SaveToFile(string filePath)
		{
			XmlSerializer ser = new XmlSerializer(typeof(Vocab));

			using (FileStream fs = new FileStream(filePath, FileMode.Create))
			{
				ser.Serialize(fs, Vocabulary);
			}
		}

		public void LoadFromFile(string filePath)
		{
			XmlSerializer ser = new XmlSerializer(typeof(Vocab));

			if (!File.Exists(filePath))
				return;


			using (FileStream fs = new FileStream(filePath, FileMode.Open))
			{
				try
				{
					Vocabulary = (Vocab)ser.Deserialize(fs);
				}
				catch (Exception ex)
				{
					MessageBox.Show(ex.ToString());
				}
			}
		}

		public void SaveToCsv(string filePath)
		{
			const string delimiter = ";";
			using (StreamWriter fs = new StreamWriter(filePath))
			{
				foreach (var record in Vocabulary.HunEngDic)
				{
					string line = "";
					line += record.Value.Eng;
					line += delimiter;
					line += record.Value.Hun;
					line += delimiter;
					line += record.Value.SuccessfulGuesses;
					line += delimiter;
					line += record.Value.TotalGuesses;
					fs.WriteLine(line);
				}
			}
		}
	}
}
